﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace UlsStudio.Engine.Parsers
{
    /// <summary>
    /// Used to tokenise a ULS logfile line entry into fields. 
    /// </summary>
    public class UlsLineParser
    {
        const int TAB_CHAR_OFFSET = 1;
        const int MAX_FIXED_POS_ULS_COLS = 6;       // There are 6 columns in ULS logs that have fixed position
        private static char[] TAB_CHAR_ARRAY = "\t".ToCharArray();

        private int[] _columnIndexCache = null;

        /// <summary>
        /// Turns a single ULS line into tokens
        /// </summary>
        public string[] GetUlsTokenFromLine(string lineIn, out int lineTokenCount)
        {
            // Init
            List<string> lineTokens = new List<string>(Constants.ULS_ENTRY_EXPECTED_TOKEN_COUNT + 1);
            System.IO.StringReader reader = new System.IO.StringReader(lineIn);

            // Build col cache if necesary
            if (_columnIndexCache == null) _columnIndexCache = GetColumnPositions(lineIn).ToArray();
            
            int fieldLength = 0;
            lineTokenCount = 0;

            // Read column cache for fixed-set columns
            for (int i = 0; i < _columnIndexCache.Length; i++)
            {
                int thisStartIndex = _columnIndexCache[i];

                string lineToken = string.Empty;

                // Is this the beginning position of the last fixed-length column in the ULS file line?
                bool lastFixedWidthColumn = (i == (_columnIndexCache.Length - 1));

                if (!lastFixedWidthColumn)
                {
                    // Normal fixed-size column
                    int nextStartIndex = _columnIndexCache[i + 1];
                    fieldLength = (nextStartIndex - thisStartIndex);

                    // Pull buffer from cache?
                    char[] lineTokenChars = new char[fieldLength];
                    reader.Read(lineTokenChars, 0, fieldLength);
                    lineToken = new string(lineTokenChars);

                    // Add fixed-length col
                    lineTokens.Add(lineToken);
                    lineTokenCount++;
                }
                else
                {
                    // Read final fixed-length segment if we've reached the last column
                    fieldLength = (lineIn.Length - thisStartIndex);
                    char[] lineTokenChars = new char[fieldLength];
                    reader.Read(lineTokenChars, 0, fieldLength);
                    lineToken = new string(lineTokenChars);

                    Logging.Writeline("Final fixed-length column taken from {0}: '{1}'", LoggingLevel.Verbose, thisStartIndex, lineToken);

                    // More tokens in last fixed-sized column? Search for non-fixed possition TAB chars
                    List<string> variableLenTokens = GetVariableLengthTokensFromString(lineToken);
                    lineTokens.AddRange(variableLenTokens);
                    lineTokenCount+= variableLenTokens.Count;
                }       // token is not fixed-width

            }

            return lineTokens.ToArray();
        }

        /// <summary>
        /// Retrieve tokens where column positions are variable.
        /// </summary>
        private List<string> GetVariableLengthTokensFromString(string lineToken)
        {
            List<string> tokens = new List<string>();

            int fieldLength = 0;
            if (lineToken.IndexOf(TAB_CHAR_ARRAY[0]) > -1)
            {
                int lastIndex = 0, newIndex = -1;
                do
                {
                    newIndex = lineToken.IndexOf(TAB_CHAR_ARRAY[0], lastIndex);
                    if (newIndex > -1)
                    {
                        newIndex++;     // Skip over char searched (tab) for next search so IndexOf doesn't return 0 for next token

                        fieldLength = newIndex - lastIndex;

                        // Grab & add variable-length token
                        string subToken = lineToken.Substring(lastIndex, fieldLength);
                        tokens.Add(subToken);

                        lastIndex = newIndex;
                    }
                    else
                    {
                        // No more tokens found; add remaining string.
                        string subToken = lineToken.Substring(lastIndex, lineToken.Length - lastIndex);
                        tokens.Add(subToken);
                    }
                } while (newIndex != -1);       // Keep going until no more columns

            }
            else
            {
                tokens.Add(lineToken);      // Add whole line
            }

            return tokens;
        }


        /// <summary>
        /// Return a list of where the fixed-length columns are in a line
        /// </summary>
        private List<int> GetColumnPositions(string lineIn)
        {
            List<int> colIndexCache = new List<int>(MAX_FIXED_POS_ULS_COLS);

            int lastIndex = 0, newIndex = -1, tokenCount = 0;
            // Find each column position
            do
            {
                newIndex = lineIn.IndexOf(TAB_CHAR_ARRAY[0], lastIndex);
                if (newIndex > -1)
                {
                    newIndex++;     // Skip over char searched (tab) for next search so IndexOf doesn't return 0 for next token

                    // Remember col possition
                    colIndexCache.Add(lastIndex);
                    lastIndex = newIndex;
                }
                tokenCount++;
            } while (newIndex != -1 && tokenCount < MAX_FIXED_POS_ULS_COLS);       // Keep going until no more fixed-poss cols or no more cols

            // Debug out
            string debugString = "There are fixed-width columns @ characters ";
            for (int i = 0; i < colIndexCache.Count; i++ )
            {
                debugString += colIndexCache[i];
                if (i + 1 < colIndexCache.Count) debugString += ", ";
            }
            //Logging.Writeline(debugString);

            return colIndexCache;
        }

        /// <summary>
        /// Formats a message if it has a stack-trace
        /// </summary>
        /// <param name="ulsMessage"></param>
        /// <returns></returns>
        public static string FormatStackTraceMessages(string ulsMessage)
        {
            const string EN_AT = "     at";
            const string ES_AT = "     en";

            if (ulsMessage.IndexOf(EN_AT) > -1)
            {
                return ulsMessage.Replace(EN_AT, Environment.NewLine + "  " + EN_AT.Trim());
            }
            else if (ulsMessage.IndexOf(ES_AT) > -1)
            {
                return ulsMessage.Replace(ES_AT, Environment.NewLine + "  " + ES_AT.Trim());
            }
            return ulsMessage;
        }
    }
}
